class GameSet(set):
    def __init__(self,name,elements=[],supersets=[],subsets=[]):
        set.__init__(self,elements)
        self.supersets = list(supersets)
        self.subsets = list(subsets)
        for s in self.supersets: s.subsets.append(self)

    # these are two different ways of checking containment (isIn is more efficient, __contains__ just allows x in gs)
    def __contains__(self,elem):
        for e in self:
            if e is elem: return True
        for subset in self.subsets:
            if elem is subset: return True
            if elem in subset: return True
        return False

    def isIn(self,gs):
        if self is gs: return True
        for ss in self.supersets:
            if ss is gs: return True
        for ss in self.supersets:
            if ss.isIn(gs): return True 

    def retrieve(self,name):
        for e in self:
            if e.name == name: return e
        for subset in self.subsets:
            if subset.name == name: return subset
            e = subset.retrieve(name)
            if e: return e
        return None

def gameClass(c):
    """Assign a GameSet to a class, so that when __init__ is invoked the instance is added to the set."""
    gs = GameSet(name=c.__name__,supersets=[superc.gameset for superc in c.__bases__ if hasattr(superc,'gameset')])
    c.oldinit = c.__init__ # this is probably really bad...
    def newinit(newself,*args,**kwargs):
        gs.add(newself)
        return c.oldinit(newself,*args,**kwargs)
    c.__init__ = newinit
    c.gameset = gs
    return c


class Entity(object):
    def __init__(self, engine, name):
        self.engine = engine
        self.name = name
    def update(self, timeStep):
        pass
    def isA(self, c):
        if hasattr(c,'gameset'): return self.gameset.isIn(c.gameset)
        return self.gameset.isIn(c)
    def destroy(self):
        pass # TODO: should this be here? A little bloaty...

Entity.gameset = GameSet('Entity')








# Some default entities




# TODO: This is currently just used as a test hack
class Box(Entity):
    def __init__(self, engine, name, pos=(0.0, 0.0), size=(0.1,0.1), density=1):
        Entity.__init__(self, engine, name)
        
        self.body = engine.physics_world.createRectBody(pos, size, density)
        #self.body.ApplyForce( b2Vec2(0.1, 0), b2Vec2(0.0, 0.1) )
        #self.body.ApplyTorque( 0.01 )
        
        self.model = engine.renderer.\
                     createModel(layer="play", \
                                 mesh="box", \
                                 texture="Box.jpg", \
                                 influences=[(self.body.GetXForm(), 1.0)], \
                                 local_xform=b2XForm_identity)
        #texture.startAnimating(engine)

    def __del__(self):
        self.destroy()

    def destroy(self):
        self.engine.renderer.destroyModel(self.model)
        self.engine.physics_world.destroyBody(self.body)
        
    def update(self, timeStep):
        pass



class CompositeBody(Entity):
    def __init__(self, engine, name):
        Entity.__init__(self, engine, name)
        
        self.bodies = {}
        self.joints = {}
        #TODO: Work out how to render the models. Requires thinking about how
        #      to specify their xform. Giving them an xform could improve the
        #      layer handling code.
        self.models = {}
        #self.model_list = []
        
        # The body that is referred to when querying the position or angle of the composite body
        self.ref_body = None

    def __del__(self):
        self.destroy()

    def destroy(self):
        for model in self.models.values():
            self.engine.renderer.destroyModel(model)
        for body in self.bodies.values():
            self.engine.physics_world.destroyBody(body)


    # TODO: Decide whether to use this and implement it
    def addModel(self, name, model):
        pass

    # Note: This must be used to ensure that the body's userData is set!
    # TODO: Consider this interface and decide whether to make self.bodies
    #       private and do the same for joints and models.
    def addBody(self, name, body, make_reference_body=False):
        body.SetUserData(self)
        if make_reference_body:
            self.ref_body = body
        self.bodies[name] = body

    def setReferenceBody(self, name):
        self.ref_body = self.bodies[name]

    def getXForm(self):
        return self.ref_body.GetXForm()
    def getPos(self):
        return self.ref_body.GetPosition()
    def getAngle(self):
        return self.ref_body.GetAngle()
    def getLinearVelocity(self):
        return self.ref_body.GetLinearVelocity()
    def getAngularVelocity(self):
        return self.ref_body.GetAngularVelocity()
    
    def setXForm(self, newXForm):
        # TODO: Check that this works
        posChange = newXForm.position - self.ref_body.GetWorldCenter()
        angleChange = newXForm.R.GetAngle() - self.ref_body.GetAngle()
        for body in self.bodies.values():
            body.SetXForm(body.GetWorldCenter() + posChange, \
                          body.GetAngle() + angleChange)
            
    def applyForce(self, force, pos):
        for body in self.bodies.values():
            body.ApplyForce(force, pos)
            
    def applyCenteredForce(self, force):
        for body in self.bodies.values():
            body.ApplyForce(force, body.GetWorldCenter())

    def applyTorque(self, torque):
        for body in self.bodies.values():
            body.ApplyTorque(torque)
            
    def applyDampingForce(self, force):
        for body in self.bodies.values():
            body.ApplyForce(force * -body.GetLinearVelocity(), body.GetWorldCenter())
    def applyDampingTorque(self, torque):
        for body in self.bodies.values():
            body.ApplyTorque(torque * -body.GetAngularVelocity())
CompositeBody = gameClass(CompositeBody)

